﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using SubSonic.DataProviders;
using System.Data.Linq;

namespace SurveyHost.Models
{
    /// <summary>
    /// A base class for all data models.
    /// </summary>
    /// <typeparam name="T">The type which extends this base class.</typeparam>
    public class BaseDataModel<T> where T : class
    {
        /// <summary>
        /// The <seealso cref="DataModelsContext"/> used in methods within <see cref="BaseDataModel{T}"/>.
        /// </summary>
        protected static DataModelsContext GetContext()
        {
            //TODO:  Make this mockable
            return _mockContext ?? new DataModelsContext();
        }
        /// <summary>
        /// The context to use during tests
        /// </summary>
        private static DataModelsContext _mockContext;

        /// <summary>
        /// Creates a new <typeparamref name="T"/> in the repository.
        /// </summary>
        /// <param name="p">The <typeparamref name="T"/> to add to the repository.</param>
        /// <returns>The added <typeparamref name="T"/>.</returns>
        public static T Create(T p)
        {
            try
            {
                using (var context = GetContext())
                {
                    context.GetTable<T>().InsertOnSubmit(p);
                    context.SubmitChanges();
                    return p;
                }
            }
            catch (ChangeConflictException cce)
            {
                throw new DataModelException("Unable to create the data model.", cce);
            }
        }

        /// <summary>
        /// Inserts a group of <typeparamref name="T">Ts</typeparamref> into the repository.
        /// </summary>
        /// <param name="p">The <typeparamref name="T">Ts</typeparamref> to add to the repository.</param>
        public static void Create(IEnumerable<T> p)
        {
            try
            {
                using (var context = GetContext())
                {
                    context.GetTable<T>().InsertAllOnSubmit(p);
                    context.SubmitChanges();
                }
            }
            catch (ChangeConflictException cce)
            {
                throw new DataModelException("Unable to create the data model.", cce);
            }
        }

        /// <summary>
        /// Updates a <typeparamref name="T"/>
        /// </summary>
        /// <param name="p">The <typeparamref name="T"/> to update</param>
        public static void Update(T p)
        {
            using (var context = GetContext())
            {
                context.GetTable<T>().Attach(p, true);
                context.SubmitChanges();
            }
        }
        /// <summary>
        /// Deletes a <typeparamref name="T"/>
        /// </summary>
        /// <param name="p">The <typeparamref name="T"/> to delete</param>
        public static void Delete(T p)
        {
            using (var context = GetContext())
            {
                context.GetTable<T>().Attach(p);
                context.GetTable<T>().DeleteOnSubmit(p);
                context.SubmitChanges();
            }
        }
        /// <summary>
        /// Deletes a group of <typeparamref name="T">Ts</typeparamref>
        /// </summary>
        /// <param name="p">The <typeparamref name="T"/>s to delete</param>
        public static void Delete(IEnumerable<T> p)
        {
            using (var context = GetContext())
            {
                var table = context.GetTable<T>();
                table.AttachAll(p);
                table.DeleteAllOnSubmit(p);
                context.SubmitChanges();
            }
        }
        /// <summary>
        /// Retrieves all <see cref="T">Ts</see> in the repository
        /// </summary>
        /// <returns>An array of <see cref="T">Ts</see></returns>
        public static T[] Get()
        {
            using (var context = GetContext())
            {
                return context.GetTable<T>().ToArray();
            }
        }
        /// <summary>
        /// Retrieves a page of <see cref="T">Ts</see> from the repository
        /// </summary>
        /// <param name="skip">The number of records to skip.</param>
        /// <param name="take">The number of records to take.</param>
        /// <returns>An array of <see cref="T">Ts</see></returns>
        public static T[] Get(int skip, int take)
        {
            using (var context = GetContext())
            {
                return context.GetTable<T>().Skip(skip).Take(take).ToArray();
            }
        }
    }

    /// <summary>
    /// The exception to thrown when there is an unrecoverable error in a data model.
    /// </summary>
    [global::System.Serializable]
    public class DataModelException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DataModelException"/> class.
        /// </summary>
        public DataModelException() { }
        /// <summary>
        /// Initializes a new instance of the <see cref="DataModelException"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public DataModelException(string message) : base(message) { }
        /// <summary>
        /// Initializes a new instance of the <see cref="DataModelException"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="inner">The inner.</param>
        public DataModelException(string message, Exception inner) : base(message, inner) { }
        /// <summary>
        /// Initializes a new instance of the <see cref="DataModelException"/> class.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception>
        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception>
        protected DataModelException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}
